package com.hsgene.common.util.lock;

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

/**
 * Redis分布式锁.
 * <p>
 * PS:暂未优化完成
 * </p>
 *
 * @author wangbing
 * @version 1.0, 2018/8/514
 */
public class RedisDistributeLock implements Serializable {

    private static final long serialVersionUID = 1669879731340390838L;
    private final static String REDIS_LOCK_PREFIX = "distribute_lock:";

    private final RedisTemplate<String, Object> redisTemplate;

    /**
     * 锁键
     */
    private final String key;

    /**
     * 锁拥有者信息
     */
    private final Object value;

    /**
     * redis键
     */
    private final String lockKey;

    /**
     * 加锁标识
     */
    private boolean locked = false;

    public RedisDistributeLock(RedisTemplate<String, Object> redisTemplate, String key, Object value) {
        this.redisTemplate = redisTemplate;
        this.key = key;
        this.value = value;
        this.lockKey = REDIS_LOCK_PREFIX + key;
    }

    /**
     * 非阻塞式加锁
     *
     * @return true：加锁成功；false：加锁失败
     */
    @SuppressWarnings("unchecked")
    public boolean lock() {
        SessionCallback<Boolean> sessionCallback = new SessionCallback<Boolean>() {
            @Override
            public Boolean execute(RedisOperations redisOperations) throws DataAccessException {
                Object lock = redisOperations.opsForValue().get(lockKey);
                Boolean locked = Boolean.FALSE;
                if (lock == null) {
                    locked = redisOperations.opsForValue().setIfAbsent(lockKey, value);
                    locked = locked == null || locked;
                    if (locked) {
                        // 锁超时时间，默认30分钟，超时锁自动释放，避免持有者长时间持有锁，而其他持有者无法获取锁的情况
                        redisOperations.expire(lockKey, 30, TimeUnit.MINUTES);
                    }
                } else if (value.equals(lock)) {
                    locked = Boolean.TRUE;
                    redisOperations.expire(lockKey, 30, TimeUnit.MINUTES);
                }
                return locked;
            }
        };

        Boolean isLocked = redisTemplate.execute(sessionCallback);
        this.locked = isLocked != null && isLocked;
        return this.locked;
    }

    /**
     * 解锁
     *
     * @return true：解锁成功；false：解锁失败
     */
    @SuppressWarnings("unchecked")
    public boolean unlock() {
        if (this.locked) {
            SessionCallback<Boolean> sessionCallback = new SessionCallback<Boolean>() {
                @Override
                public Boolean execute(RedisOperations redisOperations) throws DataAccessException {
                    Object lock = redisOperations.opsForValue().get(lockKey);
                    if (value.equals(lock)) {
                        redisOperations.delete(lockKey);
                        return Boolean.TRUE;
                    }
                    return Boolean.FALSE;
                }
            };
            Boolean isUnlocked = redisTemplate.execute(sessionCallback);
            boolean unlocked = isUnlocked != null && isUnlocked;
            if (unlocked) {
                this.locked = false;
            }
            return unlocked;
        }
        return true;
    }

    /**
     * 判断当前是否已获得锁
     *
     * @return true：已获得；false：未获得
     */
    public boolean isLocked() {
        if (this.locked) {
            SessionCallback<Boolean> sessionCallback = new SessionCallback<Boolean>() {
                @Override
                public Boolean execute(RedisOperations redisOperations) throws DataAccessException {
                    Object lock = redisOperations.opsForValue().get(lockKey);
                    if (value.equals(lock)) {
                        return Boolean.TRUE;
                    }
                    return Boolean.FALSE;
                }
            };

            Boolean isLocked = redisTemplate.execute(sessionCallback);
            this.locked = isLocked != null && isLocked;
        }

        return this.locked;
    }

    /**
     * GC执行，预防锁拥有者忘记解锁时，最后由GC执行释放持有的锁，避免死锁
     *
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        if (isLocked()) {
            unlock();
        }
        super.finalize();
    }

    public String getKey() {
        return key;
    }

    public Object getValue() {
        return value;
    }

    public String getLockKey() {
        return lockKey;
    }
}
